package com.anyext.storage.config;

import java.util.List;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import com.anyext.storage.dialect.Dialect;
import com.anyext.storage.exception.ParseException;
import com.anyext.storage.group.impl.GroupDescriptorImpl;
import com.anyext.storage.group.impl.GroupImpl;
import com.anyext.storage.policy.DataSourceType;
import com.anyext.storage.router.Router;

public class ConfigDefinitionParser extends AbstractBeanDefinitionParser {

	private enum LocalName {
		dialect, router, group;
	}

	@Override
	protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
		List<Element> childElements = DomUtils.getChildElements(element);
		for (Element childElement : childElements) {
			if (LocalName.dialect.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseDialectInternal(childElement, parserContext);
			} else if (LocalName.router.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseRouterInternal(childElement, parserContext);
			} else if (LocalName.group.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseGroupInternal(childElement, parserContext);
			}
		}
		return null;
	}

	private void parseDialectInternal(Element element, ParserContext parserContext) {
		RootBeanDefinition dialectDefinition = new RootBeanDefinition();

		String attribute = element.getAttribute(ID_ATTRIBUTE);
		if (!StringUtils.hasText(attribute)) {
			throw new ParseException("must define a identity for dialect");
		}
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(dialectDefinition, attribute);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());

		attribute = element.getAttribute("class");
		if (StringUtils.hasText(attribute)) {
			try {
				Class<?> dialectClass = ClassUtils.forName(attribute, ClassUtils.getDefaultClassLoader());
				if (Dialect.class.isAssignableFrom(dialectClass)) {
					dialectDefinition.setBeanClass(dialectClass);
				} else {
					throw new ParseException("can't case class[" + attribute + "] to [" + Dialect.class.getName() + "] for dialect[" + element.getAttribute(ID_ATTRIBUTE) + "]");
				}
			} catch (Exception e) {
				throw new ParseException("can't load class[" + attribute + "] for dialect[" + element.getAttribute(ID_ATTRIBUTE) + "]", e);
			}
		} else {
			throw new ParseException("must define a class for dialect[" + element.getAttribute(ID_ATTRIBUTE) + "]");
		}
	}

	private void parseRouterInternal(Element element, ParserContext parserContext) {
		RootBeanDefinition routerDefinition = new RootBeanDefinition();

		String attribute = element.getAttribute(ID_ATTRIBUTE);
		if (!StringUtils.hasText(attribute)) {
			throw new ParseException("must define a identity for router");
		}
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(routerDefinition, attribute);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());

		attribute = element.getAttribute("class");
		if (StringUtils.hasText(attribute)) {
			try {
				Class<?> routerClass = ClassUtils.forName(attribute, ClassUtils.getDefaultClassLoader());
				if (Router.class.isAssignableFrom(routerClass)) {
					routerDefinition.setBeanClass(routerClass);
				} else {
					throw new ParseException("can't case class[" + attribute + "] to [" + Router.class.getName() + "] for router[" + element.getAttribute(ID_ATTRIBUTE) + "]");
				}
			} catch (Exception e) {
				throw new ParseException("can't load class[" + attribute + "] for router[" + element.getAttribute(ID_ATTRIBUTE) + "]", e);
			}
		} else {
			throw new ParseException("must define a class for router[" + element.getAttribute(ID_ATTRIBUTE) + "]");
		}
	}

	private void parseGroupInternal(Element element, ParserContext parserContext) {
		RootBeanDefinition groupDefinition = new RootBeanDefinition();
		groupDefinition.setBeanClass(GroupImpl.class);

		String attribute = element.getAttribute(ID_ATTRIBUTE);
		if (!StringUtils.hasText(attribute))
			attribute = "default";
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(groupDefinition, attribute);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());
		groupDefinition.getPropertyValues().add("identity", attribute);

		attribute = element.getAttribute("dialect");
		if (StringUtils.hasText(attribute)) {
			groupDefinition.getPropertyValues().add("dialect", new RuntimeBeanReference(attribute));
		}

		attribute = element.getAttribute("router");
		if (StringUtils.hasText(attribute)) {
			groupDefinition.getPropertyValues().add("router", new RuntimeBeanReference(attribute));
		}

		List<Element> childElements = DomUtils.getChildElements(element);
		if (childElements.size() > 0) {
			ManagedMap<String, BeanDefinition> groupDescriptorsDefinition = new ManagedMap<String, BeanDefinition>();
			for (Element childElement : childElements) {
				RootBeanDefinition groupDescriptorDefinition = new RootBeanDefinition();
				groupDescriptorDefinition.setBeanClass(GroupDescriptorImpl.class);
				attribute = childElement.getAttribute(ID_ATTRIBUTE);

				idHolder = new BeanDefinitionHolder(groupDescriptorDefinition, attribute);
				BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());
				groupDescriptorDefinition.getPropertyValues().add("identity", attribute);

				attribute = childElement.getAttribute("dataSource");
				if (StringUtils.hasText(attribute)) {
					groupDescriptorDefinition.getPropertyValues().add("dataSource", new RuntimeBeanReference(attribute));
				}

				attribute = childElement.getAttribute("lbfactor");
				if (StringUtils.hasText(attribute)) {
					groupDescriptorDefinition.getPropertyValues().add("lbfactor", attribute);
				}

				attribute = childElement.getLocalName();
				if (attribute.equalsIgnoreCase(DataSourceType.Master.name())) {
					groupDescriptorDefinition.getPropertyValues().add("dataSourceType", DataSourceType.Master);
				} else {
					groupDescriptorDefinition.getPropertyValues().add("dataSourceType", DataSourceType.Slave);
				}
				groupDescriptorsDefinition.put(childElement.getAttribute(ID_ATTRIBUTE), groupDescriptorDefinition);
			}
			groupDefinition.getPropertyValues().add("groupDescriptors", groupDescriptorsDefinition);
		}
	}

}
